﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ShevaEngine.Core.Modules.Graphics;
using ShevaEngine.Core.Modules.Graphics.Cameras;
using ShevaEngine.Core.Modules.Materials;
using ShevaEngine.Core.Modules.Models.Pipeline;
using ShevaEngine.Core.Modules.Scenes;
using ShevaEngine2.Modules.Animations;
using System.Collections;
using ShevaEngine.Core.Modules.Animations;

namespace ShevaEngine.Core.Modules.Models
{
    /// <summary>
    /// Sheva model.
    /// </summary>
    public class ShevaModel : SceneNode
    {
        /// <summary>Indexer.</summary>
        private Int32 Indexer;
        /// <summary>Animation player.</summary>
        private AnimationPlayer AnimationPlayer;

        /// <summary>Is model bone animated.</summary>
        public bool IsBoneAnimated { get { return this.AnimationPlayer != null; } }
        
        /// <summary>
        /// Sheva internal model.
        /// </summary>
        /// <param name="internalModel"></param>
        public ShevaModel(ModelInternal internalModel)
            : base()
        {
            List<VertexBuffer> vertexBuffers = new List<VertexBuffer>();
            List<IndexBuffer> indexBuffers = new List<IndexBuffer>();

            foreach (ModelInternalBuffer buffer in internalModel.Buffers)
            {
                VertexBuffer newVBO = new VertexBuffer(ShevaEngine.Instance.GraphicsDevice, new VertexDeclaration(buffer.VertexElements), buffer.VerticesCount, BufferUsage.None);
                newVBO.SetData<Byte>(buffer.VertexBufferContent.ToArray());
                vertexBuffers.Add(newVBO);

                //buffer.VertexBufferContent.Clear();
                //buffer.VertexBufferContent = null;

                IndexBuffer newIBO = new IndexBuffer(ShevaEngine.Instance.GraphicsDevice, buffer.ElementSize, buffer.IndicesCount, BufferUsage.None);
                newIBO.SetData<Byte>(buffer.IndexBufferContent.ToArray());
                indexBuffers.Add(newIBO);

                //buffer.IndexBufferContent.Clear();
                //buffer.IndexBufferContent = null;
            }

            if (internalModel.SkinningData != null)
                this.AnimationPlayer = AnimationsManager.Instance.CreateAnimationPlayer(internalModel.SkinningData);

            List<BoundingSphere> boundings = new List<BoundingSphere>();

            foreach (ModelInternalNode mesh in internalModel)
            {
                SceneNode tempNode = this.CreateSceneNode(mesh, vertexBuffers, indexBuffers);

                boundings.Add(this.ComputeBoundingSphere(tempNode));

                this.Add(tempNode);
            }

            this.BoundingSphere = boundings[0];

            for (int i = 1; i < boundings.Count; i++)
                this.BoundingSphere = BoundingSphere.CreateMerged(this.BoundingSphere, boundings[i]);
        }

        /// <summary>
        /// Method creates scene node.
        /// </summary>
        /// <param name="modelNode"></param>
        /// <returns></returns>
        private SceneNode CreateSceneNode(ModelInternalNode modelNode, List<VertexBuffer> vertexBuffers, List<IndexBuffer> indexBuffers)
        {
            SceneNode newNode = new SceneNode();

            if (modelNode is ModelInternalNodeDOF)
            {
                newNode = new SceneNodeDOF();
                //(newNode as SceneNodeDOF).Transform = (modelNode as ModelInternalNodeDOF).Transform;
                (newNode as SceneNodeDOF).PutMatrix = (modelNode as ModelInternalNodeDOF).Transform;
                (newNode as SceneNodeDOF).InvPutMatrix = Matrix.Invert((modelNode as ModelInternalNodeDOF).Transform);
            }
            else if (modelNode is ModelInternalNodeExternalReference)
            {
                newNode = new SceneNodeTransformation();
                
                (newNode as SceneNodeTransformation).Transform = (modelNode as ModelInternalNodeExternalReference).Transform;
                newNode.AddRange(ModelManager.Instance[(modelNode as ModelInternalNodeExternalReference).ModelName]);                
            }
            else if (modelNode is ModelInternalNodeTransform)
            {
                newNode = new SceneNodeTransformation();
                (newNode as SceneNodeTransformation).Transform = (modelNode as ModelInternalNodeTransform).Transform;
            }
            else if (modelNode is ModelInternalNodeGroup)
            {
                newNode = new SceneNodeGroup();                
            }
            else if (modelNode is ModelInternalNodeLevelOfDetail)
            {
                newNode = new SceneNodeLevelOfDetail();
                (newNode as SceneNodeLevelOfDetail).SwitchInDistance = (modelNode as ModelInternalNodeLevelOfDetail).SwitchInDistance;
                (newNode as SceneNodeLevelOfDetail).SwitchOutDistance = (modelNode as ModelInternalNodeLevelOfDetail).SwitchOutDistance;
                (newNode as SceneNodeLevelOfDetail).CenterPoint = (modelNode as ModelInternalNodeLevelOfDetail).CenterPoint;
            }
            else if (modelNode is ModelInternalNodeSwitch)
            {
                newNode = new SceneNodeSwitch();
                (newNode as SceneNodeSwitch).CurrentMask = (modelNode as ModelInternalNodeSwitch).CurrentMask;
                foreach (Byte[] item in (modelNode as ModelInternalNodeSwitch).Masks)
                    (newNode as SceneNodeSwitch).Masks.Add(new BitArray(item));
            }            
            else if (modelNode is ModelInternalNodeGeometry)
            {
                newNode = new SceneNodeDrawCall();                

                UInt16 materialId = MaterialManager.Instance.GetMaterialID((modelNode as ModelInternalNodeGeometry).MaterialParameters.Name);

                GraphicsMaterial material = MaterialManager.Instance[materialId].GraphicsProfile;

                GraphicsMaterialParametersList graphicsParameters = new GraphicsMaterialParametersList();

                if (this.IsBoneAnimated)
                    graphicsParameters.Add(new GraphicsMaterialParameterAnimations("AnimationTransforms", this.AnimationPlayer));

                DrawCallKey drawCallKey = new DrawCallKey(
                    materialId,
                    TextureManager.Instance.GetTextureStateId(new Textures((modelNode as ModelInternalNodeGeometry).MaterialParameters.Textures)),
                    material.GetMaterialParametersId(graphicsParameters),
                    GraphicsBuffersManager.Instance.GetBuffersID(
                        vertexBuffers[(modelNode as ModelInternalNodeGeometry).BuffersIndex],
                        indexBuffers[(modelNode as ModelInternalNodeGeometry).BuffersIndex]));

                newNode.BoundingSphere = modelNode.BoundingSphere;

                (newNode as SceneNodeDrawCall).DrawCallKey = drawCallKey;
                (newNode as SceneNodeDrawCall).DrawCall = DrawCallManager.Instance.CreateIndexedPrimitivesDrawCall( 
                    (modelNode as ModelInternalNodeGeometry).PrimitiveType,
                    (modelNode as ModelInternalNodeGeometry).NumVertices,
                    (modelNode as ModelInternalNodeGeometry).PrimitiveCount,
                    (modelNode as ModelInternalNodeGeometry).StartIndex,
                    (modelNode as ModelInternalNodeGeometry).VertexOffset,
                    (modelNode as ModelInternalNodeGeometry).BaseVertex);

                return newNode;
            }            

            foreach (ModelInternalNode item in modelNode)
                newNode.Add(this.CreateSceneNode(item, vertexBuffers, indexBuffers));

            return newNode;
        }


        /// <summary>
        /// Method computes bounding sphere.
        /// </summary>
        /// <param name="node"></param>
        private BoundingSphere ComputeBoundingSphere(SceneNode node)
        {
            if (node is SceneNodeDrawCall)
                return node.BoundingSphere;

            List<BoundingSphere> boundings = new List<BoundingSphere>();

            foreach (SceneNode item in node)
            {
                BoundingSphere boundingSphere = this.ComputeBoundingSphere(item);

                if (boundingSphere != new BoundingSphere())
                    boundings.Add(boundingSphere);
            }

            node.BoundingSphere = boundings[0];

            for (int i = 1; i < boundings.Count; i++)
                node.BoundingSphere = BoundingSphere.CreateMerged(node.BoundingSphere, boundings[i]);

            //node.BoundingSphere = node.BoundingSphere.Transform(transform);

            return node.BoundingSphere;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        public void PlayAnimation(string name)
        {
            if (this.IsBoneAnimated)
                this.AnimationPlayer.StartClip(name);
        }        

        /// <summary>
        /// Method attaches scene object to bone.
        /// </summary>
        /// <param name="sceneObject"></param>
        /// <param name="boneName"></param>
        /// <returns></returns>
        public bool AttachSceneObjectToBone(AttachToAnimationBridge animationBridge, string boneName)
        {
            if (this.AnimationPlayer != null)
                return this.AnimationPlayer.AttachSceneObjectToBone(animationBridge, boneName);

            return false;
        }
    }
}
